package org.dbi.beans;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;

import org.dbi.exceptions.DuplicateRecordException;
import org.dbi.exceptions.MissingPrimaryKeyColumn;
import org.dbi.logger.ErrorLogger;

public class Tabel {
	private String naam = null;
	
	private LinkedHashMap<String, Attribute> attributen;
	private Constraint constraint = null;
	private ErrorLogger logger = ErrorLogger.getLogger(getClass().getName());
	private ArrayList<Record> records;
	
	public Tabel(String naam, Constraint cons) {
		this.naam = naam;
		constraint = cons;
		attributen = new LinkedHashMap<String, Attribute>();
		records = new ArrayList<Record>();
		logger.info("Tabel: " + naam + " aangemaakt");
	}
	
	public String getNaam() {
		return naam;
	}
	
	public Constraint getConstraint() {
		return constraint;
	}
	
	public void setConstraint(Constraint c) {
		constraint = c;
		logger.info("Nieuwe constraint waarde gezet");
		for (Key k : constraint.getKeys().values()) {
			logger.info(k.getNaam());
		}
	}
	
	public LinkedHashMap<String, Attribute> getAttributes() {
		return attributen;
	}
	
	public void addSingleAttribute(Attribute a) {
		if (!attributen.containsKey(a.getNaam())) {
			attributen.put(a.getNaam(), a);
			logger.info("Attribute: " + a.getNaam() + " is toegevoegd aan tabel: " + getNaam());
		} else {
			logger.info("Attribute: " + a.getNaam() + " bestaat al in tabel: " + getNaam());
		}
	}
	
	public void addMultipleAttributes(LinkedHashMap<String, Attribute> attributes) {
		if (attributes.size() != 0 && attributes != null) {
			for (Attribute a : attributes.values()) {
				addSingleAttribute(a);
			}
		}
	}
	
	// select * from tabel
	public ArrayList<Object[]> selectAllRecords() {
		ArrayList<Object[]> foundElements = new ArrayList<Object[]>();
		String[] columnNames = new String[attributen.size()];
		int i = 0;
		for (Attribute a : attributen.values()) {
			if (i != attributen.size()) {
				columnNames[i] = a.getNaam();
				i++;
			}
		}
		
		foundElements.add(columnNames);
		for (Record r : records) {
			foundElements.add(r.getRecordValues());
		}
		
		for (Object[] oa : foundElements)
			logger.info("values " + Arrays.toString(oa));
		return foundElements;
	}
	
	// select naam, naam from tabel
	public ArrayList<Object[]> selectSpecificColumn(String[] columnNames) {
		ArrayList<Object[]> foundElements = new ArrayList<Object[]>();
		if (checkIfColumnsExist(columnNames)) {
			int objectPosition = 0;
			foundElements.add(columnNames);
			for (Record r : records) {
				Object[] currentObject = new Object[columnNames.length];
				for (String s : columnNames) {
					currentObject[objectPosition] = r.getRecordValues()[r.columnPosition(s)];
					
					objectPosition++;
				}
				foundElements.add(currentObject);
				objectPosition = 0;
			}
			
		}
		
		for (Object[] oa : foundElements)
			logger.info(Arrays.toString(oa));
		return foundElements;
		
	}
	
	// select naam, naam from tabel where naam = value
	public ArrayList<Object[]> selectSpecificColumnWithWhere(String[] columnNames, String[] whereColumnName, Object[] whereValue) {
		
		ArrayList<Object[]> foundElements = new ArrayList<Object[]>();
		if (checkIfColumnsExist(columnNames) && checkIfColumnsExist(whereColumnName)) {
			int objectPosition = 0;
			// foundElements.add(columnNames);
			for (Record r : getRecordsWithCertainValue(whereColumnName, whereValue)) {
				Object[] currentObject = new Object[columnNames.length];
				for (String s : columnNames) {
					currentObject[objectPosition] = r.getRecordValues()[r.columnPosition(s)];
					objectPosition++;
				}
				foundElements.add(currentObject);
				objectPosition = 0;
			}
			
		}
		
		for (Object[] oa : foundElements)
			logger.info(Arrays.toString(oa));
		return foundElements;
		
	}
	
	public boolean deleteRecordWithWhere(String[] columnNames, String[] whereColumnName, Object[] whereValue) {
		
		if (checkIfColumnsExist(columnNames) && checkIfColumnsExist(whereColumnName)) {
			for (Record r : getRecordsWithCertainValue(whereColumnName, whereValue)) {
				for (String s : columnNames) {
					logger.info("Delete record: " + Arrays.toString(r.getRecordValues()) + " in tabel " + getNaam());
					records.remove(r);
				}
			}
		}
		
		return true;
		
	}
	
	public boolean addRecord(String[] columns, Object[] values) {
		System.out.println(getNaam() + " - " + Arrays.toString(columns) + " - " + Arrays.toString(values));
		boolean insert = false;
		checkValueType(values, columns);
		if (checkIfColumnsExist(columns) && columns.length == values.length && checkValueType(values, columns)) {
			logger.info("" + getNaam());
			if (!checkPrimaryKeyColumnsAndValues(getPriKey(), convertStringArrayToMapForPkCheck(columns, values), values, getAllRecordsForCheck()))
				return false;
			
			LinkedHashMap<String, String> primaryKeyAttributes = getPrimaryKeyAttributes();
			if (!checkRecordValues(primaryKeyAttributes.size(), values)) {
				String columnsEmptyOfPrimaryKey = "";
				int pkCounter = 0;
				for (String s : primaryKeyAttributes.keySet()) {
					for (int i = 0; i < columns.length; i++) {
						if (!columns[i].equals(s)) {
							columnsEmptyOfPrimaryKey += s;
						}
					}
					pkCounter++;
				}
				if (pkCounter != primaryKeyAttributes.size()) {
					try {
						throw new MissingPrimaryKeyColumn(columnsEmptyOfPrimaryKey);
					} catch (MissingPrimaryKeyColumn e) {
						// TODO Auto-generated catch block
						logger.info(e.getMessage());
					}
				} else {
					Record r = new Record(columns, values);
					records.add(r);
					insert = true;
				}
			} else {
				try {
					throw new DuplicateRecordException(values);
				} catch (DuplicateRecordException e) {
					// TODO Auto-generated catch block
					logger.info(e.getMessage());
				}
			}
			
			return insert;
			
		} else
			return false;
	}
	
	public boolean updateRecord(String[] columnsToEdit, Object[] newValues, String[] whereColumnNames, Object[] whereValues) {
		/**
		 * stap 1 controleren of nieuwe record niet al voorkomt controleren of
		 * de columnen wel bestaan controleren of de lengte van de colomnnamen
		 * en de 2 records overeenkomen controleren of de valuetypes wel kloppen
		 * met wat er binnenkomt stap 2 record aanpassen
		 */
		
		LinkedHashMap<String, String> primaryKeyAttributes = getPrimaryKeyAttributes();
		int pkSize = primaryKeyAttributes.size();
		// TODO dit moet getAttributeGroup size worden
		if (checkIfColumnsExist(columnsToEdit) && checkIfColumnsExist(whereColumnNames))
			if (columnsToEdit.length == newValues.length)
				if (whereColumnNames.length == whereValues.length) {
					for (Record r : getRecordsWithCertainValue(whereColumnNames, whereValues)) {
						Object[] myNewValues = createRecordValueArrayForComparison(columnsToEdit, newValues, r.getRecordValues().clone());
						if (!checkRecordValues(pkSize, myNewValues)) {
							
							if (r.setNewValues(myNewValues)) {
								return true;
								
							} else
								return false;
						}
					}
				}
		return false;
		
	}
	
	public LinkedHashMap<String, String> getPrimaryKeyAttributes() {
		PrimaryKey pk = null;
		LinkedHashMap<String, String> myAttribs = new LinkedHashMap<String, String>();
		for (Key k : constraint.getKeys().values()) {
			if (k instanceof PrimaryKey) {
				pk = (PrimaryKey) k;
				for (AttributeGroup ag : pk.getAttributeGroups().values()) {
					for (Attribute a : ag.getAttributes().values()) {
						myAttribs.put(a.getNaam(), a.getNaam());
					}
				}
				
			}
		}
		
		return myAttribs;
	}
	
	private PrimaryKey getPriKey() {
		PrimaryKey pk = null;
		for (Key k : constraint.getKeys().values()) {
			if (k.getNaam().startsWith("PK"))
				pk = (PrimaryKey) k;
		}
		return pk;
	}
	
	// private hulp methoden
	public Object[] createRecordValueArrayForComparison(String[] columnsToEdit, Object[] newValues, Object[] valuesCurrentRecord) {
		for (int i = 0; i < columnsToEdit.length; i++) {
			valuesCurrentRecord[columnPosition(columnsToEdit[i])] = newValues[i];
		}
		return valuesCurrentRecord;
		
	}
	
	public ArrayList<Record> getRecordsWithCertainValue(String[] whereColumnNames, Object[] whereValues) {
		
		ArrayList<Record> foundRecords = new ArrayList<Record>();
		int columnCounter = 0;
		
		for (Record r : records) {
			columnCounter = 0;
			for (String c : whereColumnNames) {
				
				for (Object v : whereValues) {
					
					if (r.getRecordValues()[r.columnPosition(c)].toString().equals(v.toString())) {
						columnCounter++;
					}
				}
			}
			
			if (columnCounter == whereColumnNames.length)
				foundRecords.add(r);
		}
		
		return foundRecords;
		
	}
	
	public int columnPosition(String columnName) {
		int counter = 0;
		for (Attribute a : attributen.values()) {
			
			if (a.getNaam().equals(columnName))
				return counter;
			else
				counter++;
		}
		return counter;
	}
	
	public boolean checkIfColumnsExist(String[] columns) {
		int columnCounter = 0;
		
		for (String s : columns) {
			
			if (attributen.containsKey(s)) {
				columnCounter++;
			}
			
		}
		
		if (columnCounter == columns.length)
			return true;
		else
			return false;
	}
	
	public boolean checkRecordValues(int pkSize, Object[] values) {
		int valueCounter = 0;
		boolean returnValue = false;
		for (Record currentRecord : records) {
			if (Arrays.equals(currentRecord.getRecordValues(), values))
				valueCounter = values.length;
			if (valueCounter != pkSize)
				returnValue = false;
			else
				returnValue = true;
		}
		return returnValue;
		
	}
	
	public boolean checkValueType(Object[] values, String[] columns) {
		ArrayList<Attribute> attrib = new ArrayList<Attribute>();
		
		// Juiste volgorde plaatsten.
		for (int i = 0; i < columns.length; i++) {
			for (Attribute a : attributen.values()) {
				if (columns[i].equals(a.getNaam())) {
					attrib.add(a);
					
				}
			}
		}
		
		int counter = 0;
		for (Attribute a : attrib) {
			if (compare(a.getColumnType(), values[counter])) {
				counter++;
			} else {
				logger.info(a.getColumnType().getClass() + "   is niet gelijk aan " + values[counter]);
				return false;
			}
			
		}
		return true;
		
	}
	
	public boolean compare(java.lang.Object o1, java.lang.Object o2) {
		
		if (o1.getClass().equals(o2.getClass())) {
			return true;
		} else {
			return false;
		}
	}
	
	public ArrayList<Record> getAllRecordsForCheck() {
		return records;
	}
	
	private boolean checkPrimaryKeyColumnsAndValues(PrimaryKey pk, LinkedHashMap<String, String> columnNames, Object[] columnValues, ArrayList<Record> myRecords) {
		ArrayList<Boolean> b = new ArrayList<Boolean>();
		int pkCounter = 0;
		for (AttributeGroup ag : pk.getAttributeGroups().values()) {
			for (Record r : myRecords) {
				for (Attribute a : ag.getAttributes().values()) {
					if (columnNames.containsKey(a.getNaam())) {
						logger.info("Gekregen ag: " + ag.getNaam());
						logger.info("Gekregen pk: " + pk.getNaam());
						logger.severe("Primarykey: " + a.getNaam() + " gevonden. Bezig met controleren van de meegeleverde waarde.");
						logger.info("keyset: " + columnNames.keySet().toString());
						logger.info("values: " + columnNames.values().toString());
						logger.info(Arrays.toString(columnValues));
						
						if (columnNames.get(a.getNaam()).toString().equals(r.getRecordValues()[recordValuePosition(a.getNaam(), r)].toString())) {
							logger.info("pk waarde : " + columnNames.get(a.getNaam()).toString() + " komt al voor");
							pkCounter++;
							if (pkCounter == getPrimaryKeyAttributes().size()) {
								logger.info("pk is gelijk " + pkCounter + " " + getPrimaryKeyAttributes().size());
								b.add(false);
								
								
							} else {
								logger.info("pk niet gelijk " + pkCounter + " " + getPrimaryKeyAttributes().size());
								
							}
						} else {
							logger.info("Waarde komt niet voor in huidige gelezen record. " + columnNames.get(a.getNaam()).toString() + " - "
							        + r.getRecordValues()[recordValuePosition(a.getNaam(), r)]);
						}
						b.add(true);
						
					} else {
						logger.info("Gekregen ag: " + ag.getNaam());
						logger.info("Gekregen pk: " + pk.getNaam());
						logger.severe("Primarykey: " + a.getNaam() + " niet gevonden.");
						logger.info(columnNames.keySet().toString());
						logger.info(Arrays.toString(columnValues));
						 b.add(false);
					}
				}
				pkCounter = 0;
			}
		}
		if (b.contains(false)) {
			return false;
		} else {
			return true;
		}
		
	}
	
	private LinkedHashMap<String, String> convertStringArrayToMapForPkCheck(String[] columnNames, Object[] values) {
		LinkedHashMap<String, String> convertedMap = new LinkedHashMap<String, String>();
		if (columnNames.length == values.length) {
			for (int i = 0; i < columnNames.length; i++)
				convertedMap.put(columnNames[i], values[i].toString());
		}
		return convertedMap;
	}
	
	public int recordValuePosition(String columnName, Record r) {
		int counter = 0;
		for (String s : r.getRecordColumnNames()) {
			if (s.equals(columnName))
				return counter;
			else
				counter++;
		}
		return counter;
	}
}